prepare-root: Factor out composefs handling into otcore
authorColin Walters <walters@verbum.org>
Thu, 19 Jun 2025 11:42:48 +0000 (07:42 -0400)
committerColin Walters <walters@verbum.org>
Thu, 19 Jun 2025 14:47:40 +0000 (10:47 -0400)
I'm thinking about creating ostree-prepare-soft-reboot.c.
Prepare for this by factoring out shared helper functions.

Makefile-otcore.am
src/libotcore/otcore-prepare-root.c
src/libotcore/otcore.h
src/switchroot/ostree-prepare-root.c

index ec34578506a4f81b84b41aab363238dc201587db..07beb1fe405adf059ddc3859f286aec77b37ad16 100644 (file)
@@ -22,5 +22,11 @@ libotcore_la_SOURCES = \
     src/libotcore/otcore-spki-verify.c \
        $(NULL)
 
-libotcore_la_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/libglnx -I$(srcdir)/src/libotutil -DLOCALEDIR=\"$(datadir)/locale\" $(OT_INTERNAL_GIO_UNIX_CFLAGS) $(OT_INTERNAL_GPGME_CFLAGS) $(OT_DEP_CRYPTO_LIBS) $(LIBSYSTEMD_CFLAGS)
+# Note that this also uses *includes* from libostree, so there's a partial
+# circular dependency.
+libotcore_la_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/libglnx -I$(srcdir)/src/libotutil -I$(srcdir)/src/libostree -DLOCALEDIR=\"$(datadir)/locale\" $(OT_INTERNAL_GIO_UNIX_CFLAGS) $(OT_INTERNAL_GPGME_CFLAGS) $(OT_DEP_CRYPTO_LIBS) $(LIBSYSTEMD_CFLAGS)
 libotcore_la_LIBADD = $(OT_INTERNAL_GIO_UNIX_LIBS) $(OT_INTERNAL_GPGME_LIBS) $(LIBSYSTEMD_LIBS) $(OT_DEP_CRYPTO_LIBS)
+
+if USE_COMPOSEFS
+libotcore_la_LIBADD += $(OT_DEP_COMPOSEFS_LIBS)
+endif
\ No newline at end of file
index 18bdf43e5663928fc615b75e172dc14d1ad2c95a..01acf5cfecc3d96349febba0e10842cf453113bb 100644 (file)
 #include "config.h"
 
 #include "otcore.h"
+#include <errno.h>
+#include <ostree-core.h>
+#include <ostree-repo-private.h>
+
+#ifdef HAVE_COMPOSEFS
+#include <libcomposefs/lcfs-mount.h>
+#include <libcomposefs/lcfs-writer.h>
+#endif
 
 // This key is used by default if present in the initramfs to verify
 // the signature on the target commit object.  When composefs is
@@ -247,3 +255,237 @@ otcore_load_composefs_config (const char *cmdline, GKeyFile *config, gboolean lo
 
   return g_steal_pointer (&ret);
 }
+
+#ifdef HAVE_COMPOSEFS
+static GVariant *
+load_variant (const char *root_mountpoint, const char *digest, const char *extension,
+              const GVariantType *type, GError **error)
+{
+  g_autofree char *path = g_strdup_printf ("%s/ostree/repo/objects/%.2s/%s.%s", root_mountpoint,
+                                           digest, digest + 2, extension);
+
+  char *data = NULL;
+  gsize data_size;
+  if (!g_file_get_contents (path, &data, &data_size, error))
+    return NULL;
+
+  return g_variant_ref_sink (g_variant_new_from_data (type, data, data_size, FALSE, g_free, data));
+}
+
+// Given a mount point, directly load the .commit object.  At the current time this tool
+// doesn't link to libostree.
+static gboolean
+load_commit_for_deploy (const char *root_mountpoint, const char *deploy_path, GVariant **commit_out,
+                        GVariant **commitmeta_out, GError **error)
+{
+  g_autoptr (GError) local_error = NULL;
+  g_autofree char *digest = g_path_get_basename (deploy_path);
+  char *dot = strchr (digest, '.');
+  if (dot != NULL)
+    *dot = 0;
+
+  g_autoptr (GVariant) commit_v
+      = load_variant (root_mountpoint, digest, "commit", OSTREE_COMMIT_GVARIANT_FORMAT, error);
+  if (commit_v == NULL)
+    return FALSE;
+
+  g_autoptr (GVariant) commitmeta_v = load_variant (root_mountpoint, digest, "commitmeta",
+                                                    G_VARIANT_TYPE ("a{sv}"), &local_error);
+  if (commitmeta_v == NULL)
+    {
+      if (g_error_matches (local_error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
+        glnx_throw (error, "No commitmeta for commit %s", digest);
+      else
+        g_propagate_error (error, g_steal_pointer (&local_error));
+      return FALSE;
+    }
+
+  *commit_out = g_steal_pointer (&commit_v);
+  *commitmeta_out = g_steal_pointer (&commitmeta_v);
+
+  return TRUE;
+}
+
+/**
+ * validate_signature:
+ * @data: The raw data whose signature must be validated
+ * @signatures: A variant of type "ay" (byte array) containing signatures
+ * @pubkeys: an array of type GBytes*
+ *
+ * Verify that @data is signed using @signatures and @pubkeys.
+ */
+static gboolean
+validate_signature (GBytes *data, GVariant *signatures, GPtrArray *pubkeys, GError **error)
+{
+  g_assert (data);
+  g_assert (signatures);
+  g_assert (pubkeys);
+
+  for (gsize j = 0; j < pubkeys->len; j++)
+    {
+      GBytes *pubkey = pubkeys->pdata[j];
+      g_assert (pubkey);
+
+      for (gsize i = 0; i < g_variant_n_children (signatures); i++)
+        {
+          g_autoptr (GVariant) child = g_variant_get_child_value (signatures, i);
+          g_autoptr (GBytes) signature = g_variant_get_data_as_bytes (child);
+          bool valid = false;
+
+          if (!otcore_validate_ed25519_signature (data, pubkey, signature, &valid, error))
+            return glnx_prefix_error (error, "signature verification failed");
+          // At least one valid signature is enough.
+          if (valid)
+            return TRUE;
+        }
+    }
+
+  return FALSE;
+}
+
+// Output a friendly message based on an errno for common cases
+static const char *
+composefs_error_message (int errsv)
+{
+  switch (errsv)
+    {
+    case ENOVERITY:
+      return "fsverity not enabled on composefs image";
+    case EWRONGVERITY:
+      return "Wrong fsverity digest in composefs image";
+    case ENOSIGNATURE:
+      return "Missing signature for fsverity in composefs image";
+    default:
+      return strerror (errsv);
+    }
+}
+
+#endif
+
+gboolean
+otcore_mount_composefs (ComposefsConfig *composefs_config, GVariantBuilder *metadata_builder,
+                        gboolean root_transient, const char *root_mountpoint,
+                        const char *deploy_path, const char *mount_target,
+                        bool *out_using_composefs, GError **error)
+{
+  bool using_composefs = FALSE;
+#ifdef HAVE_COMPOSEFS
+  /* We construct the new sysroot in /sysroot.tmp, which is either the composefs
+     mount or a bind mount of the deploy-dir */
+  if (composefs_config->enabled == OT_TRISTATE_NO)
+    return TRUE;
+
+  const char *objdirs[] = { "/sysroot/ostree/repo/objects" };
+  struct lcfs_mount_options_s cfs_options = {
+    objdirs,
+    1,
+  };
+
+  cfs_options.flags = 0;
+  cfs_options.image_mountdir = OSTREE_COMPOSEFS_LOWERMNT;
+  if (mkdirat (AT_FDCWD, OSTREE_COMPOSEFS_LOWERMNT, 0700) < 0 && errno != EEXIST)
+    return glnx_throw (error, "Failed to create %s", OSTREE_COMPOSEFS_LOWERMNT);
+
+  g_autofree char *expected_digest = NULL;
+
+  // For now we just stick the transient root on the default /run tmpfs;
+  // however, see
+  // https://github.com/systemd/systemd/blob/604b2001081adcbd64ee1fbe7de7a6d77c5209fe/src/basic/mountpoint-util.h#L36
+  // which bumps up these defaults for the rootfs a bit.
+  g_autofree char *root_upperdir
+      = root_transient ? g_build_filename (OTCORE_RUN_OSTREE_PRIVATE, "root/upper", NULL) : NULL;
+  g_autofree char *root_workdir
+      = root_transient ? g_build_filename (OTCORE_RUN_OSTREE_PRIVATE, "root/work", NULL) : NULL;
+
+  // Propagate these options for transient root, if provided
+  if (root_transient)
+    {
+      if (!glnx_shutil_mkdir_p_at (AT_FDCWD, root_upperdir, 0755, NULL, error))
+        return glnx_prefix_error (error, "Failed to create %s", root_upperdir);
+      if (!glnx_shutil_mkdir_p_at (AT_FDCWD, root_workdir, 0700, NULL, error))
+        return glnx_prefix_error (error, "Failed to create %s", root_workdir);
+
+      cfs_options.workdir = root_workdir;
+      cfs_options.upperdir = root_upperdir;
+    }
+  else
+    {
+      cfs_options.flags = LCFS_MOUNT_FLAGS_READONLY;
+    }
+
+  if (composefs_config->is_signed)
+    {
+      const char *composefs_pubkey = composefs_config->signature_pubkey;
+      g_autoptr (GVariant) commit = NULL;
+      g_autoptr (GVariant) commitmeta = NULL;
+
+      if (!load_commit_for_deploy (root_mountpoint, deploy_path, &commit, &commitmeta, error))
+        return glnx_prefix_error (error, "Error loading signatures from repo");
+
+      g_autoptr (GVariant) signatures = g_variant_lookup_value (
+          commitmeta, OSTREE_SIGN_METADATA_ED25519_KEY, G_VARIANT_TYPE ("aay"));
+      if (signatures == NULL)
+        return glnx_throw (error, "Signature validation requested, but no signatures in commit");
+
+      g_autoptr (GBytes) commit_data = g_variant_get_data_as_bytes (commit);
+      if (!validate_signature (commit_data, signatures, composefs_config->pubkeys, error))
+        return glnx_throw (error, "No valid signatures found for public key");
+
+      g_print ("composefs+ostree: Validated commit signature using '%s'\n", composefs_pubkey);
+      g_variant_builder_add (metadata_builder, "{sv}", OTCORE_RUN_BOOTED_KEY_COMPOSEFS_SIGNATURE,
+                             g_variant_new_string (composefs_pubkey));
+
+      g_autoptr (GVariant) metadata = g_variant_get_child_value (commit, 0);
+      g_autoptr (GVariant) cfs_digest_v = g_variant_lookup_value (
+          metadata, OSTREE_COMPOSEFS_DIGEST_KEY_V0, G_VARIANT_TYPE_BYTESTRING);
+      if (cfs_digest_v == NULL || g_variant_get_size (cfs_digest_v) != OSTREE_SHA256_DIGEST_LEN)
+        return glnx_throw (error, "Signature validation requested, but no valid digest in commit");
+      const guint8 *cfs_digest_buf = ot_variant_get_data (cfs_digest_v, error);
+      if (!cfs_digest_buf)
+        return glnx_prefix_error (error, "Failed to query digest");
+
+      expected_digest = g_malloc (OSTREE_SHA256_STRING_LEN + 1);
+      ot_bin2hex (expected_digest, cfs_digest_buf, g_variant_get_size (cfs_digest_v));
+
+      g_assert (composefs_config->require_verity);
+      cfs_options.flags |= LCFS_MOUNT_FLAGS_REQUIRE_VERITY;
+      g_print ("composefs: Verifying digest: %s\n", expected_digest);
+      cfs_options.expected_fsverity_digest = expected_digest;
+    }
+  else if (composefs_config->require_verity)
+    {
+      cfs_options.flags |= LCFS_MOUNT_FLAGS_REQUIRE_VERITY;
+    }
+
+  if (lcfs_mount_image (OSTREE_COMPOSEFS_NAME, mount_target, &cfs_options) == 0)
+    {
+      using_composefs = true;
+      bool using_verity = (cfs_options.flags & LCFS_MOUNT_FLAGS_REQUIRE_VERITY) > 0;
+      g_variant_builder_add (metadata_builder, "{sv}", OTCORE_RUN_BOOTED_KEY_COMPOSEFS,
+                             g_variant_new_boolean (true));
+      g_variant_builder_add (metadata_builder, "{sv}", OTCORE_RUN_BOOTED_KEY_COMPOSEFS_VERITY,
+                             g_variant_new_boolean (using_verity));
+      g_print ("composefs: mounted successfully (verity=%s)\n", using_verity ? "true" : "false");
+    }
+  else
+    {
+      int errsv = errno;
+      g_assert (composefs_config->enabled != OT_TRISTATE_NO);
+      if (composefs_config->enabled == OT_TRISTATE_MAYBE && errsv == ENOENT)
+        {
+          g_print ("composefs: No image present\n");
+        }
+      else
+        {
+          const char *errmsg = composefs_error_message (errsv);
+          return glnx_throw (error, "composefs: failed to mount: %s", errmsg);
+        }
+    }
+#else
+  /* if composefs is configured as "maybe", we should continue */
+  if (composefs_config->enabled == OT_TRISTATE_YES)
+    return glnx_throw (error, "composefs: enabled at runtime, but support is not compiled in");
+#endif
+  *out_using_composefs = using_composefs;
+  return TRUE;
+}
index 351c195ecc145ccd528372209a804781ac036014..7a9978de9df6e12c469cde815c66d5a1a3f4e795 100644 (file)
@@ -78,6 +78,27 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC (ComposefsConfig, otcore_free_composefs_config)
 ComposefsConfig *otcore_load_composefs_config (const char *cmdline, GKeyFile *config,
                                                gboolean load_keys, GError **error);
 
+/**
+ * otcore_mount_composefs:
+ * @composefs_config: Configuration for composefs.
+ * @metadata_builder: (transfer none): GVariantBuilder to add metadata to.
+ * @root_transient: Whether the root filesystem is transient.
+ * @root_mountpoint: The mount point of the physical root filesystem.
+ * @deploy_path: The path to the deployment.
+ * @mount_target: The target path to mount the composefs image.
+ * @out_using_composefs: (out): Whether composefs was successfully used.
+ * @error: (out): Return location for a GError, or %NULL.
+ *
+ * Mounts a composefs image based on the provided configuration.
+ *
+ * Returns: %TRUE on success, %FALSE on error.
+ */
+gboolean otcore_mount_composefs (ComposefsConfig *composefs_config,
+                                 GVariantBuilder *metadata_builder, gboolean root_transient,
+                                 const char *root_mountpoint, const char *deploy_path,
+                                 const char *mount_target, bool *out_using_composefs,
+                                 GError **error);
+
 // Our directory with transient state (eventually /run/ostree-booted should be a link to
 // /run/ostree/booted)
 #define OTCORE_RUN_OSTREE "/run/ostree"
index 1e77809f1b44e8556caa7a440593345f59656961..d3dee902aea67f548720f2ef369ae850bc6bd95e 100644 (file)
 // A temporary mount point
 #define TMP_SYSROOT "/sysroot.tmp"
 
-#ifdef HAVE_COMPOSEFS
-#include <libcomposefs/lcfs-mount.h>
-#include <libcomposefs/lcfs-writer.h>
-#endif
-
 #include "ostree-mount-util.h"
 
 static GOptionEntry options[] = { { NULL } };
@@ -149,113 +144,6 @@ resolve_deploy_path (const char *kernel_cmdline, const char *root_mountpoint)
   return deploy_path;
 }
 
-#ifdef HAVE_COMPOSEFS
-static GVariant *
-load_variant (const char *root_mountpoint, const char *digest, const char *extension,
-              const GVariantType *type, GError **error)
-{
-  g_autofree char *path = g_strdup_printf ("%s/ostree/repo/objects/%.2s/%s.%s", root_mountpoint,
-                                           digest, digest + 2, extension);
-
-  char *data = NULL;
-  gsize data_size;
-  if (!g_file_get_contents (path, &data, &data_size, error))
-    return NULL;
-
-  return g_variant_ref_sink (g_variant_new_from_data (type, data, data_size, FALSE, g_free, data));
-}
-
-// Given a mount point, directly load the .commit object.  At the current time this tool
-// doesn't link to libostree.
-static gboolean
-load_commit_for_deploy (const char *root_mountpoint, const char *deploy_path, GVariant **commit_out,
-                        GVariant **commitmeta_out, GError **error)
-{
-  g_autoptr (GError) local_error = NULL;
-  g_autofree char *digest = g_path_get_basename (deploy_path);
-  char *dot = strchr (digest, '.');
-  if (dot != NULL)
-    *dot = 0;
-
-  g_autoptr (GVariant) commit_v
-      = load_variant (root_mountpoint, digest, "commit", OSTREE_COMMIT_GVARIANT_FORMAT, error);
-  if (commit_v == NULL)
-    return FALSE;
-
-  g_autoptr (GVariant) commitmeta_v = load_variant (root_mountpoint, digest, "commitmeta",
-                                                    G_VARIANT_TYPE ("a{sv}"), &local_error);
-  if (commitmeta_v == NULL)
-    {
-      if (g_error_matches (local_error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
-        glnx_throw (error, "No commitmeta for commit %s", digest);
-      else
-        g_propagate_error (error, g_steal_pointer (&local_error));
-      return FALSE;
-    }
-
-  *commit_out = g_steal_pointer (&commit_v);
-  *commitmeta_out = g_steal_pointer (&commitmeta_v);
-
-  return TRUE;
-}
-
-/**
- * validate_signature:
- * @data: The raw data whose signature must be validated
- * @signatures: A variant of type "ay" (byte array) containing signatures
- * @pubkeys: an array of type GBytes*
- *
- * Verify that @data is signed using @signatures and @pubkeys.
- */
-static gboolean
-validate_signature (GBytes *data, GVariant *signatures, GPtrArray *pubkeys)
-{
-  g_assert (data);
-  g_assert (signatures);
-  g_assert (pubkeys);
-
-  for (gsize j = 0; j < pubkeys->len; j++)
-    {
-      GBytes *pubkey = pubkeys->pdata[j];
-      g_assert (pubkey);
-
-      for (gsize i = 0; i < g_variant_n_children (signatures); i++)
-        {
-          g_autoptr (GError) local_error = NULL;
-          g_autoptr (GVariant) child = g_variant_get_child_value (signatures, i);
-          g_autoptr (GBytes) signature = g_variant_get_data_as_bytes (child);
-          bool valid = false;
-
-          if (!otcore_validate_ed25519_signature (data, pubkey, signature, &valid, &local_error))
-            errx (EXIT_FAILURE, "signature verification failed: %s", local_error->message);
-          // At least one valid signature is enough.
-          if (valid)
-            return TRUE;
-        }
-    }
-
-  return FALSE;
-}
-
-// Output a friendly message based on an errno for common cases
-static const char *
-composefs_error_message (int errsv)
-{
-  switch (errsv)
-    {
-    case ENOVERITY:
-      return "fsverity not enabled on composefs image";
-    case EWRONGVERITY:
-      return "Wrong fsverity digest in composefs image";
-    case ENOSIGNATURE:
-      return "Missing signature for fsverity in composefs image";
-    default:
-      return strerror (errsv);
-    }
-}
-
-#endif
-
 int
 main (int argc, char *argv[])
 {
@@ -386,130 +274,9 @@ main (int argc, char *argv[])
 
   // Tracks if we did successfully enable it at runtime
   bool using_composefs = false;
-
-#ifdef HAVE_COMPOSEFS
-  /* We construct the new sysroot in /sysroot.tmp, which is either the composefs
-     mount or a bind mount of the deploy-dir */
-  if (composefs_config->enabled != OT_TRISTATE_NO)
-    {
-      const char *objdirs[] = { "/sysroot/ostree/repo/objects" };
-      g_autofree char *cfs_digest = NULL;
-      struct lcfs_mount_options_s cfs_options = {
-        objdirs,
-        1,
-      };
-
-      cfs_options.flags = 0;
-      cfs_options.image_mountdir = OSTREE_COMPOSEFS_LOWERMNT;
-      if (mkdirat (AT_FDCWD, OSTREE_COMPOSEFS_LOWERMNT, 0700) < 0 && errno != EEXIST)
-        err (EXIT_FAILURE, "Failed to create %s", OSTREE_COMPOSEFS_LOWERMNT);
-
-      g_autofree char *expected_digest = NULL;
-
-      // For now we just stick the transient root on the default /run tmpfs;
-      // however, see
-      // https://github.com/systemd/systemd/blob/604b2001081adcbd64ee1fbe7de7a6d77c5209fe/src/basic/mountpoint-util.h#L36
-      // which bumps up these defaults for the rootfs a bit.
-      g_autofree char *root_upperdir
-          = root_transient ? g_build_filename (OTCORE_RUN_OSTREE_PRIVATE, "root/upper", NULL)
-                           : NULL;
-      g_autofree char *root_workdir
-          = root_transient ? g_build_filename (OTCORE_RUN_OSTREE_PRIVATE, "root/work", NULL) : NULL;
-
-      // Propagate these options for transient root, if provided
-      if (root_transient)
-        {
-          if (!glnx_shutil_mkdir_p_at (AT_FDCWD, root_upperdir, 0755, NULL, &error))
-            errx (EXIT_FAILURE, "Failed to create %s: %s", root_upperdir, error->message);
-          if (!glnx_shutil_mkdir_p_at (AT_FDCWD, root_workdir, 0700, NULL, &error))
-            errx (EXIT_FAILURE, "Failed to create %s: %s", root_workdir, error->message);
-
-          cfs_options.workdir = root_workdir;
-          cfs_options.upperdir = root_upperdir;
-        }
-      else
-        {
-          cfs_options.flags = LCFS_MOUNT_FLAGS_READONLY;
-        }
-
-      if (composefs_config->is_signed)
-        {
-          const char *composefs_pubkey = composefs_config->signature_pubkey;
-          g_autoptr (GError) local_error = NULL;
-          g_autoptr (GVariant) commit = NULL;
-          g_autoptr (GVariant) commitmeta = NULL;
-
-          if (!load_commit_for_deploy (root_mountpoint, deploy_path, &commit, &commitmeta,
-                                       &local_error))
-            errx (EXIT_FAILURE, "Error loading signatures from repo: %s", local_error->message);
-
-          g_autoptr (GVariant) signatures = g_variant_lookup_value (
-              commitmeta, OSTREE_SIGN_METADATA_ED25519_KEY, G_VARIANT_TYPE ("aay"));
-          if (signatures == NULL)
-            errx (EXIT_FAILURE, "Signature validation requested, but no signatures in commit");
-
-          g_autoptr (GBytes) commit_data = g_variant_get_data_as_bytes (commit);
-          if (!validate_signature (commit_data, signatures, composefs_config->pubkeys))
-            errx (EXIT_FAILURE, "No valid signatures found for public key");
-
-          g_print ("composefs+ostree: Validated commit signature using '%s'\n", composefs_pubkey);
-          g_variant_builder_add (&metadata_builder, "{sv}",
-                                 OTCORE_RUN_BOOTED_KEY_COMPOSEFS_SIGNATURE,
-                                 g_variant_new_string (composefs_pubkey));
-
-          g_autoptr (GVariant) metadata = g_variant_get_child_value (commit, 0);
-          g_autoptr (GVariant) cfs_digest_v = g_variant_lookup_value (
-              metadata, OSTREE_COMPOSEFS_DIGEST_KEY_V0, G_VARIANT_TYPE_BYTESTRING);
-          if (cfs_digest_v == NULL || g_variant_get_size (cfs_digest_v) != OSTREE_SHA256_DIGEST_LEN)
-            errx (EXIT_FAILURE, "Signature validation requested, but no valid digest in commit");
-          const guint8 *cfs_digest_buf = ot_variant_get_data (cfs_digest_v, &error);
-          if (!cfs_digest_buf)
-            errx (EXIT_FAILURE, "Failed to query digest: %s", error->message);
-
-          expected_digest = g_malloc (OSTREE_SHA256_STRING_LEN + 1);
-          ot_bin2hex (expected_digest, cfs_digest_buf, g_variant_get_size (cfs_digest_v));
-
-          g_assert (composefs_config->require_verity);
-          cfs_options.flags |= LCFS_MOUNT_FLAGS_REQUIRE_VERITY;
-          g_print ("composefs: Verifying digest: %s\n", expected_digest);
-          cfs_options.expected_fsverity_digest = expected_digest;
-        }
-      else if (composefs_config->require_verity)
-        {
-          cfs_options.flags |= LCFS_MOUNT_FLAGS_REQUIRE_VERITY;
-        }
-
-      if (lcfs_mount_image (OSTREE_COMPOSEFS_NAME, TMP_SYSROOT, &cfs_options) == 0)
-        {
-          using_composefs = true;
-          bool using_verity = (cfs_options.flags & LCFS_MOUNT_FLAGS_REQUIRE_VERITY) > 0;
-          g_variant_builder_add (&metadata_builder, "{sv}", OTCORE_RUN_BOOTED_KEY_COMPOSEFS,
-                                 g_variant_new_boolean (true));
-          g_variant_builder_add (&metadata_builder, "{sv}", OTCORE_RUN_BOOTED_KEY_COMPOSEFS_VERITY,
-                                 g_variant_new_boolean (using_verity));
-          g_print ("composefs: mounted successfully (verity=%s)\n",
-                   using_verity ? "true" : "false");
-        }
-      else
-        {
-          int errsv = errno;
-          g_assert (composefs_config->enabled != OT_TRISTATE_NO);
-          if (composefs_config->enabled == OT_TRISTATE_MAYBE && errsv == ENOENT)
-            {
-              g_print ("composefs: No image present\n");
-            }
-          else
-            {
-              const char *errmsg = composefs_error_message (errsv);
-              errx (EXIT_FAILURE, "composefs: failed to mount: %s", errmsg);
-            }
-        }
-    }
-#else
-  /* if composefs is configured as "maybe", we should continue */
-  if (composefs_config->enabled == OT_TRISTATE_YES)
-    errx (EXIT_FAILURE, "composefs: enabled at runtime, but support is not compiled in");
-#endif
+  if (!otcore_mount_composefs (composefs_config, &metadata_builder, root_transient, root_mountpoint,
+                               deploy_path, TMP_SYSROOT, &using_composefs, &error))
+    errx (EXIT_FAILURE, "Failed to mount composefs: %s", error->message);
 
   if (!using_composefs)
     {